#include "lib/pvmsystem.h"
#include "pvm/pvmapi.h"
#include "lib/pvmstrings.h"

#include <time.h>
#include <pthread.h>
#include <stdio.h>
#include <signal.h>

#if (defined(WIN32) || defined(_WINDOWS))
#include <windows.h>
#else
#include <unistd.h>
#include <sys/timeb.h>
#endif

#include "lib/memory/pvmmemory.h"

void PVMSysLogOutString(const pvm_char* logString)
{
	fprintf(stderr, "[PVM] %s\n", (const char*)logString);
}

PVMCriticalSection* PVMGetCriticalForMmgr()
{
	return NULL;
}

void PVMFreeCriticalForMmgr()
{
}

pvm_bool PVMIsThreadSyncAvailable()
{
	return true;
}
pvm_bool PVMIsThreadsAvailable()
{
	return true;
}

pvm_bool PVMInitCritical(PVMCriticalSection *critical)
{
  return pthread_mutex_init(critical,NULL)==0;
}

void PVMDeinitCritical(PVMCriticalSection *critical)
{
  pthread_mutex_destroy(critical);
}

pvm_bool PVMEnterCritical(PVMCriticalSection *critical)
{
  return pthread_mutex_lock(critical)==0;
}

void PVMLeaveCritical(PVMCriticalSection *critical)
{
  pthread_mutex_unlock(critical);
}

void PVMInitThreadVar(PVMThreadHandle* outHandle)
{
	if(outHandle==NULL)
		return;

	outHandle->isValid = false;
}

pvm_bool PVMIsThreadValid(PVMThreadHandle threadHandle)
{
	return threadHandle.isValid;
}

PVMThreadHandle PVMGetCurrentThread()
{
	PVMThreadHandle	threadHandle;
	threadHandle.thread = pthread_self();
	threadHandle.isValid = true;

	return threadHandle;
}

pvm_bool PVMCreateThread(PVMThreadHandle* outHandle, PVMThreadStartRoutine routine, void *param)
{
	if(outHandle==NULL)
		return false;

	pthread_mutex_init(&outHandle->m_suspendMutex,NULL);
	pthread_cond_init(&outHandle->m_suspendCond, NULL);

	pthread_attr_t attr;
	pthread_attr_init(&attr);

	if(pthread_create(&outHandle->thread, &attr, routine, param)==0)
	{
		outHandle->isValid = true;
		return true;
	}

	return false;
}

void PVMSuspendThread(PVMThreadHandle threadHandle)
{
#if defined(PTW32_VERSION)
	//win32 emulation
	if(threadHandle.isValid==false)
		return;
	SuspendThread(pthread_getw32threadhandle_np(threadHandle.thread));
#else
	if(threadHandle.isValid==false)
		return;

//	pthread_kill(threadHandle.thread, SIGSTOP);

	pthread_mutex_lock(&threadHandle.m_suspendMutex);
	pthread_cond_wait(&threadHandle.m_suspendCond, &threadHandle.m_suspendMutex);
	pthread_mutex_unlock(&threadHandle.m_suspendMutex);
#endif
}

void PVMResumeThread(PVMThreadHandle threadHandle)
{
#if defined(PTW32_VERSION)
	//win32 emulation
	if(threadHandle.isValid==false)
		return;
	ResumeThread(pthread_getw32threadhandle_np(threadHandle.thread));
#else
	if(threadHandle.isValid==false)
		return;

//	pthread_kill(threadHandle.thread, SIGCONT);

	pthread_mutex_lock(&threadHandle.m_suspendMutex);
	pthread_cond_signal(&threadHandle.m_suspendCond);
	pthread_mutex_unlock(&threadHandle.m_suspendMutex);
#endif
}

int PVMJoinThread(PVMThreadHandle threadHandle)
{
	//win32 emulation
	if(threadHandle.isValid==false)
		return - 1;

	return pthread_join(threadHandle.thread, NULL);
}

void PVMSleep(pvm_uint32 sleepTime)
{
#if (defined(WIN32) || defined(_WINDOWS))
	Sleep(sleepTime);
#else
	//usleep(sleepTime * 1000); // usleep requires MICROseconds
	usleep(sleepTime); // usleep requires MICROseconds
#endif
}

pvm_uint32 PVMGetMilliSecondsCount()
{
#if (defined(WIN32) || defined(_WINDOWS))
	return GetTickCount();
#else
	timeb t;
	ftime(&t);

	return t.time*1000 + t.millitm;
#endif
}

PVMFileHandle PVMFileOpen(const pvm_char* fileName, pvm_int32 mode)
{
  pvm_char modeStr[32];
  modeStr[0] = 0;

  if(mode&PVM_READ && mode&PVM_WRITE && mode&PVM_OPEN_EXISTING)
  {
    pvm_strcpy(modeStr, _PVM_STR("r+b"));
  }
  else if(mode&PVM_READ && mode&PVM_WRITE && mode&PVM_CREATE_ALWAYS)
  {
    pvm_strcpy(modeStr, _PVM_STR("w+b"));
  }
  else if(mode&PVM_READ && mode&PVM_WRITE && mode&PVM_OPEN_ALWAYS)
  {
    pvm_strcpy(modeStr, _PVM_STR("a+b"));
  }
  else if(mode&PVM_READ && mode&PVM_OPEN_EXISTING)
  {
    pvm_strcpy(modeStr, _PVM_STR("rb"));
  }
  else if(mode&PVM_WRITE && mode&PVM_CREATE_ALWAYS)
  {
    pvm_strcpy(modeStr, _PVM_STR("wb"));
  }
  else if(mode&PVM_WRITE && mode&PVM_OPEN_ALWAYS)
  {
    pvm_strcpy(modeStr, _PVM_STR("ab"));
  }

  return fopen(fileName, modeStr);
}

void PVMFileClose(PVMFileHandle file)
{
  fclose(file);
}

pvm_bool PVMFileRead(PVMFileHandle file, void* buffer, pvm_size nBytes, pvm_size* readResult)
{
  if(readResult==NULL)
    return false;

  *readResult = 0;

  if(nBytes==0)
    return true;
  size_t res = fread(buffer, 1, nBytes, file);
  if(res!=0)
  {
    *readResult = res;
    return true;
  }
  return ferror(file)==0;
}

pvm_bool PVMFileWrite(PVMFileHandle file, void* buffer, pvm_size nBytes, pvm_size* writeResult)
{
  if(writeResult==NULL)
    return false;

  *writeResult = 0;

  if(nBytes==0)
    return true;

     size_t res = fwrite(buffer, 1, nBytes, file);

   if(res!=0)
   {
     *writeResult = res;
     return true;
   }
   else
   {
     return false;
   }
}

pvm_file_size PVMFileSize(PVMFileHandle file)
{
  pvm_file_size curOffset = PVMFileTell(file);
  if(curOffset==PVM_INVALID_FILE_SIZE)
    return PVM_INVALID_FILE_SIZE;

  if(!PVMFileSeek(file, 0, EPVM_SEEK_END))
    return PVM_INVALID_FILE_SIZE;

  pvm_file_size size = PVMFileTell(file);

  PVMFileSeek(file, curOffset, EPVM_SEEK_BEGIN);

  return size;
}

pvm_bool PVMFileSeek(PVMFileHandle file, pvm_file_size pos, pvm_int32 origin)
{
  int whence = 0;
  switch(origin)
  {
  case EPVM_SEEK_BEGIN:
    whence = SEEK_SET;
    break;
  case EPVM_SEEK_CURRENT:
    whence = SEEK_CUR;
    break;
  case EPVM_SEEK_END:
    whence = SEEK_END;
    break;
  };
#if (defined(WIN32) || defined(_WINDOWS))
  return fseek(file, pos, whence)==0;
#else
  return fseeko(file, pos, whence)==0;
#endif
}

pvm_file_size PVMFileTell(PVMFileHandle file)
{
#if (defined(WIN32) || defined(_WINDOWS))
	long offset = ftell(file);
#else
	off_t offset = ftello(file);
#endif
  if(offset==-1)
    return PVM_INVALID_FILE_SIZE;

  return offset;
}

pvm_bool PVMInitEvent(PVMEventHandle *event)
{
	if (pthread_mutex_init(&event->mutex, 0) == 0)
	{
		if (pthread_cond_init(&event->cond, 0) == 0)
		{
			event->triggered = false;
			return true;
		}
	}
	return false;
}

void PVMDeinitEvent(PVMEventHandle *event) {
	pthread_mutex_lock(&event->mutex);
    event->triggered = false;
    pthread_mutex_unlock(&event->mutex);
}

pvm_bool PVMWaitForEvent(PVMEventHandle *event) {
	pthread_mutex_lock(&event->mutex);
	while (!event->triggered) {
		pthread_cond_wait(&event->cond, &event->mutex);
	}
	event->triggered = false; 
	pthread_mutex_unlock(&event->mutex);
	return true;
}

void PVMSignalEvent(PVMEventHandle *event) {
	pthread_mutex_lock(&event->mutex);
    event->triggered = true;
    pthread_cond_signal(&event->cond);
    pthread_mutex_unlock(&event->mutex);
}

pvm_bool PVMSystemInit()
{
	return true;
}

void PVMSystemDeinit()
{
}
